/*
 * Copyright (C) 2014-2017 Qiujuer <qiujuer@live.cn>
 * WebSite http://www.qiujuer.net
 * Author qiujuer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <multimedia/image/image_pixel_map.h>
#include <malloc.h>
#include <cstdlib>
#include <cstring>

#define ABS(a)((a) < (0) ? (-a) : (a))
#define MAX(a,b)((a) > (b) ? (a) : (b))
#define MIN(a,b)((a) < (b) ? (a) : (b))
#define TAG "net.qiujuer.genius.graphics.Blur"
const int NU_M256 = 256;
const int NU_M2 = 2;
const int NU_M3 = 3;
const int NU_M5 = 5;
const int NU_M11 = 11;
const int NU_M8 = 8;
const int NU_M16 = 16;

/*************************************************
Function:		BlurArgb8888
Description:    Using stack way blurred image pixels
Calls:          malloc
Table Accessed: NULL
Table Updated:	NULL
Input:          Collection of pixels, wide image, image is high, the blur radius
Output:         After return to fuzzy collection of pixels
Return:         After return to fuzzy collection of pixels
Others:         NULL
*************************************************/
int * BlurArgb8888(int * pix, int w, int h, int radius)
{
    int wm = w - 1;
    int hm = h - 1;
    int wh = w * h;
    int div = radius + radius + 1;

    size_t sizeOfShort = sizeof(short);
    size_t sizeOfInt = sizeof(int);

    short * r = (short *)malloc(wh * sizeOfShort);
    short * g = (short *)malloc(wh * sizeOfShort);
    short * b = (short *)malloc(wh * sizeOfShort);
    int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;

    int * vmin = (int *)malloc(MAX(w, h) * sizeOfInt);

    int divsum = (div + 1) >> 1;
    divsum *= divsum;
    short * dv = (short *)malloc(NU_M256 * divsum * sizeOfShort);
    for (i = 0; i < NU_M256 * divsum; i++) {
        dv[i] = (short)(i / divsum);
    }

    yw = yi = 0;

    int (* stack)[3] = (int (*)[3])malloc(div * NU_M3 * sizeOfInt);
    int stackpointer;
    int stackstart;
    int * sir = NULL;
    int rbs;
    int r1 = radius + 1;
    int routsum, goutsum, boutsum;
    int rinsum, ginsum, binsum;

    for (y = 0; y < h; y++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        for (i = -radius; i <= radius; i++) {
            p = pix[yi + (MIN(wm, MAX(i, 0)))];
            sir = stack[i + radius];
            sir[0] = (p & 0xff0000) >> NU_M16;
            sir[1] = (p & 0x00ff00) >> NU_M8;
            sir[NU_M2] = (p & 0x0000ff);

            rbs = r1 - ABS(i);
            rsum += sir[0] * rbs;
            gsum += sir[1] * rbs;
            bsum += sir[NU_M2] * rbs;
            if (i > 0) {
                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[NU_M2];
            } else {
                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[NU_M2];
            }
        }
        stackpointer = radius;

        for (x = 0; x < w; x++) {
            r[yi] = dv[rsum];
            g[yi] = dv[gsum];
            b[yi] = dv[bsum];

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack[stackstart % div];

            routsum -= sir[0];
            goutsum -= sir[1];
            boutsum -= sir[NU_M2];

            if (y == 0) {
                vmin[x] = MIN(x + radius + 1, wm);
            }
            p = pix[yw + vmin[x]];

            sir[0] = (p & 0xff0000) >> NU_M16;
            sir[1] = (p & 0x00ff00) >> NU_M8;
            sir[NU_M2] = (p & 0x0000ff);

            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[NU_M2];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack[(stackpointer) % div];

            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[NU_M2];

            rinsum -= sir[0];
            ginsum -= sir[1];
            binsum -= sir[NU_M2];

            yi++;
        }
        yw += w;
    }
    for (x = 0; x < w; x++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        yp = -radius * w;
        for (i = -radius; i <= radius; i++) {
            yi = MAX(0, yp) + x;

            sir = stack[i + radius];

            sir[0] = r[yi];
            sir[1] = g[yi];
            sir[NU_M2] = b[yi];

            rbs = r1 - ABS(i);

            rsum += r[yi] * rbs;
            gsum += g[yi] * rbs;
            bsum += b[yi] * rbs;

            if (i > 0) {
                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[NU_M2];
            } else {
                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[NU_M2];
            }

            if (i < hm) {
                yp += w;
            }
        }
        yi = x;
        stackpointer = radius;
        for (y = 0; y < h; y++) {
            // Preserve alpha channel: ( 0xff000000 & pix[yi] )
            pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << NU_M16) | (dv[gsum] << NU_M8) | dv[bsum];

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack[stackstart % div];

            routsum -= sir[0];
            goutsum -= sir[1];
            boutsum -= sir[NU_M2];

            if (x == 0) {
                vmin[y] = MIN(y + r1, hm) * w;
            }
            p = x + vmin[y];

            sir[0] = r[p];
            sir[1] = g[p];
            sir[NU_M2] = b[p];

            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[NU_M2];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack[stackpointer];

            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[NU_M2];

            rinsum -= sir[0];
            ginsum -= sir[1];
            binsum -= sir[NU_M2];

            yi += w;
        }
    }

    free(r);
    free(g);
    free(b);
    free(vmin);
    free(dv);
    free(stack);
    return (pix);
}

/*************************************************
Function:		blur_RGB_565
Description:    Using stack way blurred image pixels
Calls:          malloc
Table Accessed: NULL
Table Updated:	NULL
Input:          Collection of pixels, wide image, image is high, the blur radius
Output:         After return to fuzzy collection of pixels
Return:         After return to fuzzy collection of pixels
Others:         NULL
*************************************************/
short * BlurRgb565(short * pix, int w, int h, int radius)
{
    int wm = w - 1;
    int hm = h - 1;
    int wh = w * h;
    int div = radius + radius + 1;

    size_t sizeOfShort = sizeof(short);
    size_t sizeOfInt = sizeof(int);

    short * r = (short *)malloc(wh * sizeOfShort);
    short * g = (short *)malloc(wh * sizeOfShort);
    short * b = (short *)malloc(wh * sizeOfShort);

    int rsum, gsum, bsum, x, y, p, i, yp, yi, yw;

    int * vmin = (int *)malloc(MAX(w, h) * sizeOfInt);

    int divsum = (div + 1) >> 1;
    divsum *= divsum;

    short * dv = (short *)malloc(NU_M256 * divsum * sizeOfShort);

    for (i = 0; i < NU_M256 * divsum; i++) {
        dv[i] = (short)(i / divsum);
    }

    yw = yi = 0;

    int (* stack)[3] = (int (*)[3])malloc(div * NU_M3 * sizeOfInt);
    int stackpointer;
    int stackstart;
    int * sir;
    int rbs;
    int r1 = radius + 1;
    int routsum, goutsum, boutsum;
    int rinsum, ginsum, binsum;

    for (y = 0; y < h; y++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        for (i = -radius; i <= radius; i++) {
            p = pix[yi + (MIN(wm, MAX(i, 0)))];
            sir = stack[i + radius];
            sir[0] = (((p) & 0xF800) >> NU_M11) << NU_M3;
            sir[1] = (((p) & 0x7E0) >> NU_M5) << NU_M2;
            sir[NU_M2] = ((p) & 0x1F) << NU_M3;

            rbs = r1 - ABS(i);
            rsum += sir[0] * rbs;
            gsum += sir[1] * rbs;
            bsum += sir[NU_M2] * rbs;
            if (i > 0) {
                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[NU_M2];
            } else {
                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[NU_M2];
            }
        }
        stackpointer = radius;

        for (x = 0; x < w; x++) {
            r[yi] = dv[rsum];
            g[yi] = dv[gsum];
            b[yi] = dv[bsum];

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack[stackstart % div];

            routsum -= sir[0];
            goutsum -= sir[1];
            boutsum -= sir[NU_M2];

            if (y == 0) {
                vmin[x] = MIN(x + radius + 1, wm);
            }
            p = pix[yw + vmin[x]];

            sir[0] = (((p) & 0xF800) >> NU_M11) << NU_M3;
            sir[1] = (((p) & 0x7E0) >> NU_M5) << NU_M2;
            sir[NU_M2] = ((p) & 0x1F) << NU_M3;

            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[NU_M2];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack[(stackpointer) % div];

            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[NU_M2];

            rinsum -= sir[0];
            ginsum -= sir[1];
            binsum -= sir[NU_M2];

            yi++;
        }
        yw += w;
    }
    for (x = 0; x < w; x++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        yp = -radius * w;
        for (i = -radius; i <= radius; i++) {
            yi = MAX(0, yp) + x;

            sir = stack[i + radius];

            sir[0] = r[yi];
            sir[1] = g[yi];
            sir[NU_M2] = b[yi];

            rbs = r1 - ABS(i);

            rsum += r[yi] * rbs;
            gsum += g[yi] * rbs;
            bsum += b[yi] * rbs;

            if (i > 0) {
                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[NU_M2];
            } else {
                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[NU_M2];
            }

            if (i < hm) {
                yp += w;
            }
        }
        yi = x;
        stackpointer = radius;
        for (y = 0; y < h; y++) {
            // Not have alpha channel
            pix[yi] = ((((dv[rsum]) >> NU_M3) << NU_M11) | (((dv[gsum]) >> NU_M2) << NU_M5) | ((dv[bsum]) >> NU_M3));

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack[stackstart % div];

            routsum -= sir[0];
            goutsum -= sir[1];
            boutsum -= sir[NU_M2];

            if (x == 0) {
                vmin[y] = MIN(y + r1, hm) * w;
            }
            p = x + vmin[y];

            sir[0] = r[p];
            sir[1] = g[p];
            sir[NU_M2] = b[p];

            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[NU_M2];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack[stackpointer];

            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[NU_M2];

            rinsum -= sir[0];
            ginsum -= sir[1];
            binsum -= sir[NU_M2];

            yi += w;
        }
    }

    free(r);
    free(g);
    free(b);
    free(vmin);
    free(dv);
    free(stack);
    return (pix);
}

int * ClipBlurArgb8888(int * pix, const int w, const int h,const int radius, const int parts)
{
    int radiusSize = w * radius;
    int diameterSize = radiusSize * NU_M2;
    int partsHeight = h / parts;
    int surplusHeight = h % parts;

    size_t sizeOfInt = sizeof(int);
    int * tempR = (int *)malloc(diameterSize * sizeOfInt);
    int * tempE = (int *)malloc(radiusSize * sizeOfInt);

    int * temp = NULL;
    int tempLen;

    for (int i = 0; i < parts; i++) {
        int isNotFirst = i == 0 ? 0 : 1;
        int isNotEnd = i == parts - 1 ? 0 : 1;

        int startIndex = i == 0 ? 0 : partsHeight * w * i - radiusSize;

        temp = &pix[startIndex];
        int onceHeight = (partsHeight + radius * (isNotFirst && isNotEnd ? NU_M2 : 1));
        if (!isNotEnd) {
            onceHeight += surplusHeight;
        }

        tempLen = onceHeight * w;

        if (isNotEnd) {
            memcpy(tempR, &temp[tempLen - diameterSize], sizeOfInt * diameterSize);
        }

        BlurArgb8888(temp, w, onceHeight, radius);

        if (isNotFirst) {
            memcpy(temp, tempE, sizeOfInt * radiusSize);
        }
        if (isNotEnd) {
            memcpy(tempE, &temp[tempLen - diameterSize], sizeOfInt * radiusSize);
            memcpy(&temp[tempLen - diameterSize], tempR, sizeOfInt * diameterSize);
        }
    }

    free(tempR);
    free(tempE);
    return (pix);
}

short * ClipBlurRgb565(short * pix, const int w, const int h,const int radius, const int parts)
{
    int radiusSize = w * radius;
    int diameterSize = radiusSize * NU_M2;
    int partsHeight = h / parts;
    int surplusHeight = h % parts;

    size_t sizeOfInt = sizeof(short);
    short * tempR = (short *)malloc(diameterSize * sizeOfInt);
    short * tempE = (short *)malloc(radiusSize * sizeOfInt);

    short * temp = NULL;
    int tempLen;

    for (int i = 0; i < parts; i++) {
        int isNotFirst = i == 0 ? 0 : 1;
        int isNotEnd = i == parts - 1 ? 0 : 1;

        int startIndex = i == 0 ? 0 : partsHeight * w * i - radiusSize;

        temp = &pix[startIndex];
        int onceHeight = (partsHeight + radius * (isNotFirst && isNotEnd ? NU_M2 : 1));
        if (!isNotEnd) {
            onceHeight += surplusHeight;
        }

        tempLen = onceHeight * w;

        if (isNotEnd) {
            memcpy(tempR, &temp[tempLen - diameterSize], sizeOfInt * diameterSize);
        }

        BlurRgb565(temp, w, onceHeight, radius);

        if (isNotFirst) {
            memcpy(temp, tempE, sizeOfInt * radiusSize);
        }
        if (isNotEnd) {
            memcpy(tempE, &temp[tempLen - diameterSize], sizeOfInt * radiusSize);
            memcpy(&temp[tempLen - diameterSize], tempR, sizeOfInt * diameterSize);
        }
    }

    free(tempR);
    free(tempE);
    return (pix);
}

extern "C" JNIEXPORT void JNICALL
Java_net_qiujuer_genius_graphics_Blur_nativeStackBlurPixels(JNIEnv * env, jclass type,jintArray pixels_, jint w, jint h,jint r)
{
// get it
jint * pixels = (* env).GetIntArrayElements(pixels_, NULL);

// check
if (pixels == NULL) {
return;
}

// blur
pixels = BlurArgb8888(pixels, w, h, r);


// release
(* env).ReleaseIntArrayElements(pixels_, pixels, 0);
}
extern "C" JNIEXPORT void JNICALL
Java_net_qiujuer_genius_graphics_Blur_nativeStackBlurBitmap(JNIEnv * env, jclass type,
jobject bitmap, jint r) {
struct OhosPixelMapInfo infoIn;
void * pixels;

// Get bitmap info
if (GetImageInfo(env, bitmap, infoIn) != OHOS_IMAGE_RESULT_SUCCESS) {
return;
}

// Check bitmap
if (infoIn.pixelFormat != OHOS_PIXEL_MAP_FORMAT_RGBA_8888 &&
infoIn.pixelFormat != OHOS_PIXEL_MAP_FORMAT_RGB_565) {
return;
}
// Lock the bitmap
if (AccessPixels(env, bitmap, & pixels) != OHOS_IMAGE_RESULT_SUCCESS) {
return;
}

// Size
int h = infoIn.height;
int w = infoIn.width;

// Blur
if (infoIn.pixelFormat == OHOS_PIXEL_MAP_FORMAT_RGBA_8888) {
pixels = BlurArgb8888((int *)(pixels), w, h, r);
} else if (infoIn.pixelFormat == OHOS_PIXEL_MAP_FORMAT_RGB_565) {
pixels = BlurRgb565((short *)(pixels), w, h, r);
}

// Unlocks everything
UnAccessPixels(env, bitmap);
}

extern "C" JNIEXPORT void JNICALL
Java_net_qiujuer_genius_graphics_Blur_nativeStackBlurBitmapClip(JNIEnv * env, jclass type, jobject bitmap, jint r, jint parts) {
OhosPixelMapInfo infoIn;
void * pixels;

// Get bitmap info
if (GetImageInfo(env, bitmap, infoIn) != OHOS_IMAGE_RESULT_SUCCESS) {
return;
}

// Check bitmap
if (infoIn.pixelFormat != OHOS_PIXEL_MAP_FORMAT_RGBA_8888 &&
infoIn.pixelFormat != OHOS_PIXEL_MAP_FORMAT_RGB_565) {
return;
}

// Lock the bitmap
if (AccessPixels(env, bitmap, & pixels) != OHOS_IMAGE_RESULT_SUCCESS) {
return;
}

// Size
int h = infoIn.height;
int w = infoIn.width;

// Blur by default
if (infoIn.pixelFormat == OHOS_PIXEL_MAP_FORMAT_RGBA_8888) {
pixels = ClipBlurArgb8888((int *) pixels, w, h, r, parts);
} else if (infoIn.pixelFormat == OHOS_PIXEL_MAP_FORMAT_RGB_565) {
pixels = ClipBlurRgb565((short *) pixels, w, h, r, parts);
}

// Unlocks everything
UnAccessPixels(env, bitmap);
}